﻿using System;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using System.Security.Principal;
using System.Web;
using System.Web.Mvc;
using System.Web.Security;
using System.Web.UI;
using CodeCamp.Infrastructure;


namespace CodeCamp.Controllers
{

  [HandleError]
  public class AccountController : Controller
  {

    // This constructor is used by the MVC framework to instantiate the controller using
    // the default forms authentication and membership providers.

    public AccountController()
      : this( null, null )
    {
    }

    // This constructor is not used by the MVC framework but is instead provided for ease
    // of unit testing this type. See the comments at the end of this file for more
    // information.
    public AccountController( IFormsAuthentication formsAuth, IMembershipService service )
    {
      FormsAuth = formsAuth ?? new FormsAuthenticationService();
      MembershipService = service ?? new AccountMembershipService();
    }

    public IFormsAuthentication FormsAuth
    {
      get;
      private set;
    }

    public IMembershipService MembershipService
    {
      get;
      private set;
    }

    public ActionResult SignIn()
    {

      return View();
    }

    [AcceptVerbs( HttpVerbs.Post )]
    [System.Diagnostics.CodeAnalysis.SuppressMessage( "Microsoft.Design", "CA1054:UriParametersShouldNotBeStrings",
        Justification = "Needs to take same parameter type as Controller.Redirect()" )]
    public ActionResult SignIn( string userName, string password, bool rememberMe, string returnUrl, string eventName )
    {

      if ( !ValidateLogOn( userName, password ) )
      {
        return View();
      }

      FormsAuth.SignIn( userName, rememberMe );
      PromotePreviousPresenterToCurrentEvent( userName, eventName );

      if ( !String.IsNullOrEmpty( returnUrl ) )
      {
        return Redirect( returnUrl );
      }
      else
      {
        return RedirectToAction( "Index", "Home" );
      }
    }

    private void PromotePreviousPresenterToCurrentEvent( string userName, string eventName )
    {
      int eventId = EventsHelper.GetEventId( eventName );
      var repository = new CodeCampDataRepository();
      var presenter = repository.GetPresenter( eventId, userName );

      if ( presenter == null )
      {
        // TODO: Generalize this to the most recent previous event at which the user was a presenter

        // Get presenter record for previous event
        var oldPresenter = repository.GetPresenter( 1, userName );

        // Add presenter record for current event
        var newPresenter = new CodeCamp.Entities01.Presenter
        {
          EventId = eventId,
          UserIdentityName = oldPresenter.UserIdentityName,
          FirstName = oldPresenter.FirstName,
          LastName = oldPresenter.LastName,
          EmailAddress = oldPresenter.EmailAddress,
          TwitterName = oldPresenter.TwitterName,
          Biography = oldPresenter.Biography,
          BlogUrl = oldPresenter.BlogUrl,
          WebsiteUrl = oldPresenter.WebsiteUrl,
          ShirtSize = oldPresenter.ShirtSize,
        };

        repository.Add( newPresenter );
        repository.Save();
      }
    }

    public ActionResult SignOut()
    {

      FormsAuth.SignOut();

      return RedirectToAction( "Index", "Home" );
    }

    public ActionResult Create()
    {

      ViewData["PasswordLength"] = MembershipService.MinPasswordLength;

      return View();
    }

    [AcceptVerbs( HttpVerbs.Post )]
    public ActionResult Create( string userName, string email, string password, string confirmPassword )
    {

      ViewData["PasswordLength"] = MembershipService.MinPasswordLength;

      if ( ValidateRegistration( userName, email, password, confirmPassword ) )
      {
        // Attempt to register the user
        MembershipCreateStatus createStatus = MembershipService.CreateUser( userName, password, email );

        if ( createStatus == MembershipCreateStatus.Success )
        {
          FormsAuth.SignIn( userName, false /* createPersistentCookie */);
          return RedirectToAction( "Index", "Home" );
        }
        else
        {
          ModelState.AddModelError( "_FORM", ErrorCodeToString( createStatus ) );
        }
      }

      // If we got this far, something failed, redisplay form
      return View();
    }

    [Authorize]
    public ActionResult ChangePassword()
    {

      ViewData["PasswordLength"] = MembershipService.MinPasswordLength;

      return View();
    }

    public ActionResult PasswordReset()
    {
        return View();
    }

    [HttpPost]
    public ActionResult PasswordReset(string email)
    {

        ViewData["PasswordReset"] = false;

        if (string.IsNullOrEmpty(email)) {
            ModelState.AddModelError("email", "Please enter an email address");
            return View();
        }

        var result = false;

        try
        {
            result = MembershipHelper.ResetPassword(email);

            if (!result)
                ModelState.AddModelError("FORM", "This account could not be retrieved, please reenter your email and try again");
            else
                ViewData["PasswordReset"] = true;

        } catch (Exception ex)
        {
            ModelState.AddModelError("FORM", ex.Message);    
        }
        
        return View();

    }

    [Authorize]
    [AcceptVerbs( HttpVerbs.Post )]
    [System.Diagnostics.CodeAnalysis.SuppressMessage( "Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes",
        Justification = "Exceptions result in password not being changed." )]
    public ActionResult ChangePassword( string currentPassword, string newPassword, string confirmPassword )
    {

      ViewData["PasswordLength"] = MembershipService.MinPasswordLength;

      if ( !ValidateChangePassword( currentPassword, newPassword, confirmPassword ) )
      {
        return View();
      }

      try
      {
        if ( MembershipService.ChangePassword( User.Identity.Name, currentPassword, newPassword ) )
        {
          return RedirectToAction( "ChangePasswordSuccess" );
        }
        else
        {
          ModelState.AddModelError( "_FORM", "The current password is incorrect or the new password is invalid." );
          return View();
        }
      }
      catch
      {
        ModelState.AddModelError( "_FORM", "The current password is incorrect or the new password is invalid." );
        return View();
      }
    }

    public ActionResult ChangePasswordSuccess()
    {

      return View();
    }

    protected override void OnActionExecuting( ActionExecutingContext filterContext )
    {
      if ( filterContext.HttpContext.User.Identity is WindowsIdentity )
      {
        throw new InvalidOperationException( "Windows authentication is not supported." );
      }
    }

    #region Validation Methods

    private bool ValidateChangePassword( string currentPassword, string newPassword, string confirmPassword )
    {
      if ( String.IsNullOrEmpty( currentPassword ) )
      {
        ModelState.AddModelError( "currentPassword", "You must specify a current password." );
      }
      if ( newPassword == null || newPassword.Length < MembershipService.MinPasswordLength )
      {
        ModelState.AddModelError( "newPassword",
            String.Format( CultureInfo.CurrentCulture,
                 "You must specify a new password of {0} or more characters.",
                 MembershipService.MinPasswordLength ) );
      }

      if ( !String.Equals( newPassword, confirmPassword, StringComparison.Ordinal ) )
      {
        ModelState.AddModelError( "_FORM", "The new password and confirmation password do not match." );
      }

      return ModelState.IsValid;
    }

    private bool ValidateLogOn( string userName, string password )
    {
      if ( String.IsNullOrEmpty( userName ) )
      {
        ModelState.AddModelError( "username", "You must specify a username." );
      }
      if ( String.IsNullOrEmpty( password ) )
      {
        ModelState.AddModelError( "password", "You must specify a password." );
      }
      if ( !MembershipService.ValidateUser( userName, password ) )
      {
        ModelState.AddModelError( "_FORM", "The username or password provided is incorrect." );
      }

      return ModelState.IsValid;
    }

    private bool ValidateRegistration( string userName, string email, string password, string confirmPassword )
    {
      if ( String.IsNullOrEmpty( userName ) )
      {
        ModelState.AddModelError( "username", "You must specify a username." );
      }
      if ( String.IsNullOrEmpty( email ) )
      {
        ModelState.AddModelError( "email", "You must specify an email address." );
      }
      if ( password == null || password.Length < MembershipService.MinPasswordLength )
      {
        ModelState.AddModelError( "password",
            String.Format( CultureInfo.CurrentCulture,
                 "You must specify a password of {0} or more characters.",
                 MembershipService.MinPasswordLength ) );
      }
      if ( !String.Equals( password, confirmPassword, StringComparison.Ordinal ) )
      {
        ModelState.AddModelError( "_FORM", "The new password and confirmation password do not match." );
      }
      return ModelState.IsValid;
    }

    private static string ErrorCodeToString( MembershipCreateStatus createStatus )
    {
      // See http://msdn.microsoft.com/en-us/library/system.web.security.membershipcreatestatus.aspx for
      // a full list of status codes.
      switch ( createStatus )
      {
        case MembershipCreateStatus.DuplicateUserName:
          return "Username already exists. Please enter a different user name.";

        case MembershipCreateStatus.DuplicateEmail:
          return "A username for that e-mail address already exists. Please enter a different e-mail address.";

        case MembershipCreateStatus.InvalidPassword:
          return "The password provided is invalid. Please enter a valid password value.";

        case MembershipCreateStatus.InvalidEmail:
          return "The e-mail address provided is invalid. Please check the value and try again.";

        case MembershipCreateStatus.InvalidAnswer:
          return "The password retrieval answer provided is invalid. Please check the value and try again.";

        case MembershipCreateStatus.InvalidQuestion:
          return "The password retrieval question provided is invalid. Please check the value and try again.";

        case MembershipCreateStatus.InvalidUserName:
          return "The user name provided is invalid. Please check the value and try again.";

        case MembershipCreateStatus.ProviderError:
          return "The authentication provider returned an error. Please verify your entry and try again. If the problem persists, please contact your system administrator.";

        case MembershipCreateStatus.UserRejected:
          return "The user creation request has been canceled. Please verify your entry and try again. If the problem persists, please contact your system administrator.";

        default:
          return "An unknown error occurred. Please verify your entry and try again. If the problem persists, please contact your system administrator.";
      }
    }
    #endregion
  }

  // The FormsAuthentication type is sealed and contains static members, so it is difficult to
  // unit test code that calls its members. The interface and helper class below demonstrate
  // how to create an abstract wrapper around such a type in order to make the AccountController
  // code unit testable.

  public interface IFormsAuthentication
  {
    void SignIn( string userName, bool createPersistentCookie );
    void SignOut();
  }

  public class FormsAuthenticationService : IFormsAuthentication
  {
    public void SignIn( string userName, bool createPersistentCookie )
    {
      FormsAuthentication.SetAuthCookie( userName, createPersistentCookie );
    }
    public void SignOut()
    {
      FormsAuthentication.SignOut();
    }
  }

  public interface IMembershipService
  {
    int MinPasswordLength { get; }

    bool ValidateUser( string userName, string password );
    MembershipCreateStatus CreateUser( string userName, string password, string email );
    bool ChangePassword( string userName, string oldPassword, string newPassword );
  }

  public class AccountMembershipService : IMembershipService
  {
    private MembershipProvider _provider;

    public AccountMembershipService()
      : this( null )
    {
    }

    public AccountMembershipService( MembershipProvider provider )
    {
      _provider = provider ?? Membership.Provider;
    }

    public int MinPasswordLength
    {
      get
      {
        return _provider.MinRequiredPasswordLength;
      }
    }

    public bool ValidateUser( string userName, string password )
    {
      return _provider.ValidateUser( userName, password );
    }

    public MembershipCreateStatus CreateUser( string userName, string password, string email )
    {
      MembershipCreateStatus status;
      _provider.CreateUser( userName, password, email, null, null, true, null, out status );
      return status;
    }

    public bool ChangePassword( string userName, string oldPassword, string newPassword )
    {
      MembershipUser currentUser = _provider.GetUser( userName, true /* userIsOnline */);
      return currentUser.ChangePassword( oldPassword, newPassword );
    }
  }
}
